package com.bogu.view;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint.Align;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.widget.TextView;

public class TextViewMultilineEllipse extends TextView {

	private TextPaint mTextPaint;
	private String mText;
	private int mAscent;
	private String mStrEllipsis;
	private String mStrEllipsisMore;
	private int mMaxLines;
	private boolean mDrawEllipsizeMoreString;
	private int mColorEllipsizeMore;
	private boolean mRightAlignEllipsizeMoreString;
	private boolean mExpanded;
	private LineBreaker mBreakerExpanded;
	private LineBreaker mBreakerCollapsed;
	/** hashMapW是优化的关键点，通过哈希表来减少计算次数 */
	private HashMap<Integer, Integer> hashMapW = new HashMap<Integer, Integer>();

	public TextViewMultilineEllipse(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
//		final Resources.Theme theme = context.getTheme();
//		TypedArray a = theme.obtainStyledAttributes(
//                  attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
//		TypedArray appearance = null;
//    int n = a.getIndexCount();
////		 for (int i = 0; i < n; i++) {
////	            int attr = a.get;
////
////	            switch (attr) {
////			 case com.android.internal.R.styleable.TextView_text:
////	                temTextxt = a.getText(attr);
////	                break;
////	                }
	    
		mExpanded = false;
		mDrawEllipsizeMoreString = true;
		mRightAlignEllipsizeMoreString = false;
		mMaxLines = -1;
		mStrEllipsis = "...";
		mStrEllipsisMore = "";
		mColorEllipsizeMore = 0xFF0000FF;

		mBreakerExpanded = new LineBreaker();
		mBreakerCollapsed = new LineBreaker();

		// Default font size and color.
		mTextPaint = new TextPaint();
		mTextPaint.setAntiAlias(true);
		mTextPaint.setTextSize(13);
		mTextPaint.setColor(0xFF000000);
		mTextPaint.setTextAlign(Align.LEFT);
		setDrawingCacheEnabled(true);
		set();
	}

	public TextViewMultilineEllipse(Context context, AttributeSet attrs) {
		super(context, attrs);
		mExpanded = false;
		mDrawEllipsizeMoreString = true;
		mRightAlignEllipsizeMoreString = false;
		mMaxLines = -1;
		mStrEllipsis = "...";
		mStrEllipsisMore = "";
		mColorEllipsizeMore = 0xFF0000FF;

		mBreakerExpanded = new LineBreaker();
		mBreakerCollapsed = new LineBreaker();

		// Default font size and color.
		mTextPaint = new TextPaint();
		mTextPaint.setAntiAlias(true);
		mTextPaint.setTextSize(13);
		mTextPaint.setColor(0xFF000000);
		mTextPaint.setTextAlign(Align.LEFT);
		setDrawingCacheEnabled(true);
		set();
	}

	public TextViewMultilineEllipse(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		mExpanded = false;
		mDrawEllipsizeMoreString = true;
		mRightAlignEllipsizeMoreString = false;
		mMaxLines = -1;
		mStrEllipsis = "...";
		mStrEllipsisMore = "";
		mColorEllipsizeMore = 0xFF0000FF;

		mBreakerExpanded = new LineBreaker();
		mBreakerCollapsed = new LineBreaker();

		// Default font size and color.
		mTextPaint = new TextPaint();
		mTextPaint.setAntiAlias(true);
		mTextPaint.setTextSize(13);
		mTextPaint.setColor(0xFF000000);
		mTextPaint.setTextAlign(Align.LEFT);
		setDrawingCacheEnabled(true);
		set();
	}
	
	void set(){
//		 this.setLayoutParams(lp);//限制TextView的宽高  
		this.setEllipsis("...");//...替换剩余字符串  
		this.setMaxLines(3);  
		this.setTextSize(TypedValue.COMPLEX_UNIT_SP,12f);
		this.setTextSize((int)this.getTextSize());
		this.setTextColor(0xff888888);  
		this.setText(" ");//设置文本  
	}

	/**
	 * Sets the text to display in this widget.
	 * 
	 * @param text
	 *            The text to display.
	 */
	public void setText(String text) {
		mText = text;
		requestLayout();
		invalidate();
	}

	/**
	 * Sets the text size for this widget.
	 * 
	 * @param size
	 *            Font size.
	 */
	public void setTextSize(int size) {
		mTextPaint.setTextSize(size);
		requestLayout();
		invalidate();
	}

	/**
	 * Sets the text color for this widget.
	 * 
	 * @param color
	 *            ARGB value for the text.
	 */
	public void setTextColor(int color) {
		mTextPaint.setColor(color);
		invalidate();
	}

	/**
	 * The string to append when ellipsizing. Must be shorter than the available
	 * width for a single line!
	 * 
	 * @param ellipsis
	 *            The ellipsis string to use, like "...", or "-----".
	 */
	public void setEllipsis(String ellipsis) {
		mStrEllipsis = ellipsis;
	}

	/**
	 * Optional extra ellipsize string. This
	 * 
	 * @param ellipsisMore
	 */
	public void setEllipsisMore(String ellipsisMore) {
		mStrEllipsisMore = ellipsisMore;
	}

	/**
	 * The maximum number of lines to allow, height-wise.
	 * 
	 * @param maxLines
	 */
	public void setMaxLines(int maxLines) {
		mMaxLines = maxLines;
	}

	/**
	 * Turn drawing of the optional ellipsizeMore string on or off.
	 * 
	 * @param drawEllipsizeMoreString
	 *            Yes or no.
	 */
	public void setDrawEllipsizeMoreString(boolean drawEllipsizeMoreString) {
		mDrawEllipsizeMoreString = drawEllipsizeMoreString;
	}

	/**
	 * Font color to use for the optional ellipsizeMore string.
	 * 
	 * @param color
	 *            ARGB color.
	 */
	public void setColorEllpsizeMore(int color) {
		mColorEllipsizeMore = color;
	}

	/**
	 * When drawing the ellipsizeMore string, either draw it wherever
	 * ellipsizing on the last line occurs, or always right align it. On by
	 * default.
	 * 
	 * @param rightAlignEllipsizeMoreString
	 *            Yes or no.
	 */
	public void setRightAlignEllipsizeMoreString(
			boolean rightAlignEllipsizeMoreString) {
		mRightAlignEllipsizeMoreString = rightAlignEllipsizeMoreString;
	}

	/**
	 * @see android.view.View#measure(int, int)
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}

	/**
	 * Determines the width of this view
	 * 
	 * @param measureSpec
	 *            A measureSpec packed into an int
	 * @return The width of the view, honoring constraints from measureSpec
	 */
	private int measureWidth(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		if (specMode == MeasureSpec.EXACTLY) {
			// We were told how big to be.
			result = specSize;

			// Format the text using this exact width, and the current mode.
			breakWidth(specSize);
		} else {
			if (specMode == MeasureSpec.AT_MOST) {
				// Use the AT_MOST size - if we had very short text, we may need
				// even less
				// than the AT_MOST value, so return the minimum.
				result = breakWidth(specSize);
				result = Math.min(result, specSize);
			} else {
				// We're not given any width - so in this case we assume we have
				// an unlimited
				// width?
				breakWidth(specSize);
			}
		}

		return result;
	}

	/**
	 * Determines the height of this view
	 * 
	 * @param measureSpec
	 *            A measureSpec packed into an int
	 * @return The height of the view, honoring constraints from measureSpec
	 */
	private int measureHeight(int measureSpec) {
		int result = 0;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);

		mAscent = (int) mTextPaint.ascent();
		if (specMode == MeasureSpec.EXACTLY) {
			// We were told how big to be, so nothing to do.
			result = specSize;
		} else {
			// The lines should already be broken up. Calculate our max desired
			// height
			// for our current mode.
			int numLines;
			if (mExpanded) {
				numLines = mBreakerExpanded.getLines().size();
			} else {
				numLines = mBreakerCollapsed.getLines().size();
			}
			result = numLines * (int) (-mAscent + mTextPaint.descent())
					+ getPaddingTop() + getPaddingBottom();

			// Respect AT_MOST value if that was what is called for by
			// measureSpec.
			if (specMode == MeasureSpec.AT_MOST) {
				result = Math.min(result, specSize);
			}
		}
		return result;
	}

	/**
	 * Render the text
	 * 
	 * @see android.view.View#onDraw(android.graphics.Canvas)
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		List<int[]> lines;
		LineBreaker breaker;
		if (mExpanded) {
			breaker = mBreakerExpanded;
			lines = mBreakerExpanded.getLines();
		} else {
			breaker = mBreakerCollapsed;
			lines = mBreakerCollapsed.getLines();
		}

		float x = getPaddingLeft();
		float y = getPaddingTop() + (-mAscent);
		for (int i = 0; i < lines.size(); i++) {
			// Draw the current line.
			int[] pair = lines.get(i);
			
			int start= pair[0], end=pair[1] + 1;
			if(mText==null)mText="";
			if(mText.length()<end)end=mText.length();
			if(start>end)start=end;
			if(start<0)start=0;
			canvas.drawText(mText, start, end, x, y, mTextPaint);

			// Draw the ellipsis if necessary.
			if (i == lines.size() - 1) {
				if (breaker.getRequiredEllipsis()) {
					canvas.drawText(mStrEllipsis,
							x + breaker.getLengthLastEllipsizedLine(), y,
							mTextPaint);
					if (mDrawEllipsizeMoreString) {
						int lastColor = mTextPaint.getColor();
						mTextPaint.setColor(mColorEllipsizeMore);
						if (mRightAlignEllipsizeMoreString) {
							// Seems to not be right...
							canvas.drawText(
									mStrEllipsisMore,
									canvas.getWidth()
											- (breaker.getLengthEllipsisMore()
													+ getPaddingRight() + getPaddingLeft()),
									y, mTextPaint);
						} else {
							canvas.drawText(
									mStrEllipsisMore,
									x
											+ breaker
													.getLengthLastEllipsizedLinePlusEllipsis(),
									y, mTextPaint);
						}
						mTextPaint.setColor(lastColor);
					}
				}
			}

			y += (-mAscent + mTextPaint.descent());
			if (y > canvas.getHeight()) {
				break;
			}
		}
	}

	public boolean getIsExpanded() {
		return mExpanded;
	}

	public void expand() {
		mExpanded = true;
		requestLayout();
		invalidate();
	}

	public void collapse() {
		mExpanded = false;
		requestLayout();
		invalidate();
	}

	private int breakWidth(int availableWidth) {
		if (hashMapW.containsKey(availableWidth))
			return hashMapW.get(availableWidth);

		int widthUsed = 0;
		if (mExpanded) {
			widthUsed = mBreakerExpanded.breakText(mText, availableWidth
					- getPaddingLeft() - getPaddingRight(), mTextPaint);
		} else {
			widthUsed = mBreakerCollapsed.breakText(mText, mStrEllipsis,
					mStrEllipsisMore, mMaxLines, availableWidth
							- getPaddingLeft() - getPaddingRight(), mTextPaint);
		}
		hashMapW.put(availableWidth, widthUsed + getPaddingLeft()
				+ getPaddingRight());
		return widthUsed + getPaddingLeft() + getPaddingRight();
	}

	/**
	 * Used internally to break a string into a list of integer pairs. The pairs
	 * are start and end locations for lines given the current available layout
	 * width.
	 */
	private static class LineBreaker {
		/** Was the input text long enough to need an ellipsis? */
		private boolean mRequiredEllipsis;

		/** Beginning and end indices for the input string. */
		private ArrayList<int[]> mLines;

		/**
		 * The width in pixels of the last line, used to draw the ellipsis if
		 * necessary.
		 */
		private float mLengthLastLine;

		/**
		 * The width of the ellipsis string, so we know where to draw the
		 * ellipsisMore string if necessary.
		 */
		private float mLengthEllipsis;

		/** The width of the ellipsizeMore string, same use as above. */
		private float mLengthEllipsisMore;

		public LineBreaker() {
			mRequiredEllipsis = false;
			mLines = new ArrayList<int[]>();
		}

		/**
		 * Used for breaking text in 'expanded' mode, which needs no ellipse.
		 * Uses as many lines as is necessary to accomodate the entire input
		 * string.
		 * 
		 * @param input
		 *            String to be broken.
		 * @param maxWidth
		 *            Available layout width.
		 * @param tp
		 *            Current paint object with styles applied to it.
		 */
		public int breakText(String input, int maxWidth, TextPaint tp) {
			return breakText(input, null, null, -1, maxWidth, tp);
		}

		/**
		 * Used for breaking text, honors ellipsizing. The string will be broken
		 * into lines using the available width. The last line will subtract the
		 * physical width of the ellipsis string from maxWidth to reserve room
		 * for the ellipsis. If the ellpsisMore string is set, then space will
		 * also be reserved for its length as well.
		 * 
		 * @param input
		 *            String to be broken.
		 * @param ellipsis
		 *            Ellipsis string, like "..."
		 * @param ellipsisMore
		 *            Optional space reservation after the ellipsis, like
		 *            " Read More!"
		 * @param maxLines
		 *            Max number of lines to allow before ellipsizing.
		 * @param maxWidth
		 *            Available layout width.
		 * @param tp
		 *            Current paint object with styles applied to it.
		 */
		public int breakText(String input, String ellipsis,
				String ellipsisMore, int maxLines, int maxWidth, TextPaint tp) {
			mLines.clear();
			mRequiredEllipsis = false;
			mLengthLastLine = 0.0f;
			mLengthEllipsis = 0.0f;
			mLengthEllipsisMore = 0.0f;

			// If maxWidth is -1, interpret that as meaning to render the string
			// on a single
			// line. Skip everything.
			if (maxWidth == -1) {
				mLines.add(new int[] { 0, input.length() });
				return (int) (tp.measureText(input) + 0.5f);
			}

			// Measure the ellipsis string, and the ellipsisMore string if
			// valid.
			if (ellipsis != null) {
				mLengthEllipsis = tp.measureText(ellipsis);
			}
			if (ellipsisMore != null) {
				mLengthEllipsisMore = tp.measureText(ellipsisMore);
			}

			// Start breaking.
			int posStartThisLine = -1;
			float lengthThisLine = 0.0f;
			boolean breakWords = true;
			int pos = 0;
			while (pos < input.length()) {

				if (posStartThisLine == -1) {
					posStartThisLine = pos;
				}

				if (mLines.size() == maxLines) {
					mRequiredEllipsis = true;
					break;
				}

				float widthOfChar = tp.measureText(input.charAt(pos) + "");
				boolean newLineRequired = false;

				if (!hasChinese(input)) {
					/** english */
					// Check for a new line character or if we've run over max
					// width.
					if (input.charAt(pos) == '\n') {
						newLineRequired = true;

						// We want the current line to go up to the character
						// right before the
						// new line char, and we want the next line to start at
						// the char after
						// this new line char.
						mLines.add(new int[] { posStartThisLine, pos - 1 });
					} else if (lengthThisLine + widthOfChar * 2 >= maxWidth) {
						newLineRequired = true;
						// We need to backup if we are in the middle of a word.
						if (input.charAt(pos) == ' ' || breakWords == false) {
							// Backup one character, because it doesn't fit on
							// this line.
							pos--;

							// So this line includes up to the character before
							// the space.
							mLines.add(new int[] { posStartThisLine, pos });
						} else {
							// Backup until we are at a space.
							Log.v("*******",
									"*********************************now char = "
											+ input.charAt(pos));
							while (input.charAt(pos) != ' ') {
								pos--;
							}

							// This line includes up to the space.
							mLines.add(new int[] { posStartThisLine, pos });
						}
					}
				} else {
					/** chinese */
					// Check for a new line character or if we've run over max
					// width.
					if (input.charAt(pos) == '\n') {
						newLineRequired = true;

						// We want the current line to go up to the character
						// right before the
						// new line char, and we want the next line to start at
						// the char after
						// this new line char.
						mLines.add(new int[] { posStartThisLine, pos - 1 });
					} else if (lengthThisLine + widthOfChar * 2 >= maxWidth) {
						newLineRequired = true;
						// This line includes up to the space.
						mLines.add(new int[] { posStartThisLine, pos });
					}
				}

				if (newLineRequired) {
					// The next cycle should reset the position if it sees it's
					// -1 (to whatever i is).
					posStartThisLine = -1;

					// Reset line length for next iteration.
					lengthThisLine = 0.0f;

					// When we get to the last line, subtract the width of the
					// ellipsis.
					if (mLines.size() == maxLines - 1) {
						maxWidth -= (mLengthEllipsis + mLengthEllipsisMore);
						// We also don't need to break on a full word, it'll
						// look a little
						// cleaner if all breaks on the final lines break in the
						// middle of
						// the last word.
						breakWords = false;
					}
				} else {
					if (!hasChinese(input)) {
						/** english */
						lengthThisLine += widthOfChar;
					} else {
						/** chinese */
						lengthThisLine += (widthOfChar + 0.5f);
					}

					// If we're on the last character of the input string, add
					// on whatever we have leftover.
					if (pos == input.length() - 1) {
						mLines.add(new int[] { posStartThisLine, pos });
					}
				}

				pos++;
			}

			// If we ellipsized, then add the ellipsis string to the end.
			if (mRequiredEllipsis) {
				int[] pairLast = mLines.get(mLines.size() - 1);
				mLengthLastLine = tp.measureText(input.substring(pairLast[0],
						pairLast[1] + 1));
			}

			// If we required only one line, return its length, otherwise we
			// used
			// whatever the maxWidth supplied was.
			if (mLines.size() == 0) {
				return 0;
			} else if (mLines.size() == 1) {
				return (int) (tp.measureText(input) + 0.5f);
			} else {
				return maxWidth;
			}
		}

		public boolean getRequiredEllipsis() {
			return mRequiredEllipsis;
		}

		public List<int[]> getLines() {
			return mLines;
		}

		public float getLengthLastEllipsizedLine() {
			return mLengthLastLine;
		}

		public float getLengthLastEllipsizedLinePlusEllipsis() {
			return mLengthLastLine + mLengthEllipsis;
		}

		public float getLengthEllipsis() {
			return mLengthEllipsis;
		}

		public float getLengthEllipsisMore() {
			return mLengthEllipsisMore;
		}

		/**
		 * 判断文本中是否含有中文
		 */
		private boolean hasChinese(String input) {
			return input.getBytes().length != input.length();
		}
	}

}